# -*- coding:utf-8 -*-

"""
用例url支持用例文件中第一列用: "导航到枫桥路;city=开封"方式配置.
用例url由: base_url + base_conf + domain_conf + case_conf组成.
base_url即: http://10.30.3.15:8080/service/iss?text=
base_conf即全局统一默认配置, 字典格式.
domain_conf是deploy中的领域配置参数, 也是词典.
case_conf是具体用例如: 导航到枫桥路;city=开封, 取{'city':'开封'}
这样三个字典(重复项以case>domain>extend的为准)append到base_url中组成一个用例url.


2018.12.11
新增参数支持大小写. (showCCI.)
但是appkey强制转小写, 否则会提示"appkey缺失或无效"

2019.5.20
dialog领域分组运行, 并发加速
"""

import os
import re
import sys
import uuid
import json
import time
import urllib2
import cPickle
import hashlib
reload(sys)
sys.setdefaultencoding('utf8')
from ConfigParser import ConfigParser as Parser


class Domain():
    def __init__(self):
        self.name         = ""
        self.conf         = ""
        self.case_file    = ""

        self.is_dialog    = False
        self.thread_num   = 1
        self.case_list    = [] # isDialog=True > 按连续且相同的udid分组
        self.total        = 0
        self.pass_        = 0
        self.recall       = 0
        self.fail         = 0
        self.timeout      = 0
        self.pass_rate    = 0.0
        self.total_time   = 0
        self.resp_time    = 0
        self.elapsed_time = 0
        self.tps          = 0

class TestCase():
    def __init__(self):
        # 所属领域(领域文件名), 用例文本, URL, 检查点, URL返回值, 是否Pass
        self.txt           = ""
        self.url           = ""
        self.udid          = ""
        self.encode_url    = "" # 对text和city等汉字进行urlEncode后的url.
        self.kv_checks     = ""
        self.recall_point  = "" # 从kv_checks提取service=<.>作为找回检查领域.
        self.resp_result   = ""
        self.unmatch_list  = []
        self.resp_time     = 0.0
        self.req_code      = "" # 请求成功==200, 超时==req.code
        self.is_timeout    = False
        self.is_pass       = False
        self.recall_pass   = False # 是否召回成功
        self.recall_detail = None # 召回失败信息

class DomainConvertor():
    Counter = 0 # UDID计数.
    UDID_Prefix = 'JUnit-test' # 避免被记录

    def __init__(self, config_file):
        cf = Parser()
        cf.read(config_file)
        self.ip = cf.get("Test_Setting", "ip")
        self.port = cf.get("Test_Setting","port")
        self.thread_num = int(cf.get("Test_Setting", "thread_num"))
        self.deploy_file = cf.get("Test_Setting","deploy_file")

        self.secret_save_file = cf.get("Secret_Setting", "secret_save_file")
        self.domain_list = []
        self.base_conf_dict = {
            # default: ishuoshuo-android key
            'appkey' : '5s2kvpyz3tcovdq63vfxravlppm4isyy2cual6il',
            'time'   : '', # 转换用例和请求有很大的时差, 设置time为空
            # 'time'   : self._timestamp(), 
            'method' : 'iss.getTalk',
            'ver'    : '2.0',
            'udid'   : '',
            'city'   : '',
            'gps'    : '',
            'dpi'    : '',
            'appsig' : '',
            'history': '',
            'voiceid': '',
            'appver' : ''
            }
        self.params_in_signature = (
                    'appkey', 'method', 'ver', 'udid', 
                    'gps', 'appver', 'text', 'history', 
                    'city', 'time', 'voiceid', 'scenario', 
                    'screen', 'dpi', 'platform', 'viewid')

        self.secret_dict = self._get_all_secrets()

    def _timestamp(self):
        return time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))

    def _get_all_secrets(self):
        return cPickle.load(file(self.secret_save_file))

    def _get_app_secret(self, appkey):
        secret = ""
        if self.secret_dict.has_key(appkey):
            secret = self.secret_dict[appkey]
        else:
            print 'No secret for [%s]' % appkey
        return secret.lower()

    def get_domain_list(self):
        ''' 按照deploy_test_conf.txt中的领域配置, 转换领域和用例. 返回长列表. 先获取配置, 初始化domain_list(领域名, 多进程判断)'''

        domain_list = []
        for ln in file(self.deploy_file):
            if ln.strip() and not ln.startswith('#'):
                domain = Domain()
                line_parts = self._get_ln_parts_without_empty(ln)
                domain.case_file = line_parts[0]
                domain.name = os.path.basename(line_parts[0])
                domain.thread_num = self.thread_num
                domain.conf = ""
                if len(line_parts) > 1:
                    domain.conf = line_parts[1].strip()
                if 'isdialog=true' in domain.conf.lower():
                    domain.is_dialog = True
                    domain.thread_num = 1
                domain_list.append(domain)
        # 初始化用例列表
        for domain in domain_list:
            self.add_case(domain)
        return domain_list

    def add_case(self, domain):
        ''' 打开用例文件, 添加用例到domain.case_list.'''
        for ln in open(domain.case_file):
            ln_strip = ln.strip()
            if not ln_strip or ln_strip.startswith('#'):
                # skip注释&空行
                continue
            else:
                self._add_to_case_list(domain, ln)

    def _add_to_case_list(self, domain, ln):
        tc = TestCase()
        ln_parts = self._get_ln_parts_without_empty(ln)
        txt_and_conf = ln_parts[0].strip()
        tc.txt = txt_and_conf.split(';')[0]
        tc.url  = self._get_case_url(txt_and_conf, domain)
        self.encode_case_url(tc)
        if len(ln_parts) > 1:
            tc.kv_checks = ln_parts[1].strip()
            tc.recall_point = self._get_service_from_check_str(tc.kv_checks)
        else:
            tc.kv_checks = ""

        domain.case_list.append(tc)
        domain.total += 1
        self.Counter += 1

    def encode_case_url(self, tc):
        encoded_txt = urllib2.quote(tc.txt)
        tc.encode_url = tc.url.replace(tc.txt, encoded_txt)
        # request url(eurl) comes from encode CHS URL(caseURL)
        city = tc.encode_url.split('city=')[1].split('&')[0]
        time = tc.encode_url.split('time=')[1].split('&')[0]
        
        if city:
            tc.encode_url = tc.encode_url.replace(city, urllib2.quote(city))
        if time:# 时间里的冒号, 破折号, 下划线, 百分号不做encode
            tc.encode_url = tc.encode_url.replace(time, urllib2.quote(time, safe='/_:-%'))

    def _get_service_from_check_str(self, check_str):
        """多组检查点: 返回 service=video||service=audio"""
        recall_checks = []
        for x in check_str.split(';'):
            x = x.strip()
            if x.startswith('service='):
                recall_checks.append(x)
        return "||".join(recall_checks)

    def _get_ln_parts_without_empty(self, ln):
        # 确保返回的ln_parts没有空字符串, 原deploy_test_conf有两个tab.
        ln_parts = []
        ln = ln.strip()
        splitter = '\t'
        if splitter not in ln and ' ' in ln:
            splitter = ' '
        ln_splits = ln.split(splitter)
        for split in ln_splits:
            if split:
                ln_parts.append(split)
        return ln_parts

    def _init_url(self):
        return 'http://%s:%s/nlu-manage/iss?text=' % (self.ip, self.port)

    def _get_dict_from_conf(self, conf):
        dict_ = {}
        if conf:
            for each_conf in conf.strip().split(';'):
                each_pair = each_conf.split('=')
                if len(each_pair) > 1:
                    k = each_pair[0]
                    v = each_pair[1]
                    dict_[k] = v
        return dict_

    def _merge_dict(self, dict_first, dict_sec):
        ''' 合并词典, key, value 大小写敏感, 需要先比较小写, 相同就替换.
            appkey强制转小写.
        '''
        mrg_dict = {}
        for k,v in dict_first.items():
            mrg_dict[k] = v
        for k,v in dict_sec.items():
            for k_m in mrg_dict.keys():
                if k.lower() == k_m.lower():
                    del mrg_dict[k_m]
                    mrg_dict[k] = v
                else:
                    mrg_dict[k] = v
        for k,v in mrg_dict.items():
            if k.lower() == 'appkey':
                del mrg_dict[k]
                mrg_dict['appkey'] = v
                break
        return mrg_dict

    def _get_domain_dict(self, domain_conf):
        domain_dict = self._get_dict_from_conf(domain_conf)
        return self._merge_dict(self.base_conf_dict, domain_dict)

    def _url_kv_dict(self, domain_conf, case_conf):
        dm_dict = self._get_domain_dict(domain_conf)
        cs_dict = self._get_dict_from_conf(case_conf)
        return self._merge_dict(dm_dict, cs_dict)

    def _get_case_url(self, case_text_with_conf, domain):
        '''1. udid=Prefix-fname-local-global-seq;
           2. isDialog, 单线程，udid不变.
           3. 如果指定udid, 那么使用指定的udid.
        '''
        case_conf = ''
        conf_split = case_text_with_conf.split(';', 1)
        case_text = conf_split[0]
        if len(conf_split) > 1:
            case_conf = conf_split[1]

        init_url = self._init_url()
        for k, v in self._url_kv_dict(domain.conf, case_conf).items():
            init_url += '&%s=%s' % (k,v)
        
        udid = '%s-%s-%d' % (self.UDID_Prefix, domain.name, self.Counter)
        if domain.is_dialog:
            udid = '%s-%s-00000' % (self.UDID_Prefix, domain.name)
        return self._get_signed_url(init_url, case_text, udid)

    def _get_signed_url(self, init_url, case_text, udid):
        prefix_url, params_dict = self._get_prefix_url_and_params(init_url, case_text, udid)
        if params_dict.has_key('appsig'):
            del params_dict['appsig']
        signature = self._get_signature(params_dict)
        signature = "appsig=%s" % signature
        for k,v in params_dict.items():
            prefix_url += "%s=%s&" % (k,v)
        
        return prefix_url + signature

    def _get_prefix_url_and_params(self, init_url, case_text, udid):
        split_url = init_url.split('iss?')
        prefix_url = split_url[0] + 'iss?'
        params_dict = {}

        for k_v in split_url[1].split('&'):
            key, value = k_v.split('=', 1)
            # key = key.lower()
            params_dict[key] = value
        # update case text and udid:
        params_dict['text'] = case_text
        if not params_dict['udid']:
            params_dict['udid'] = udid
        return prefix_url, params_dict

    def _get_signature(self, params_dict):
        secret = ''
        for k in params_dict.keys():
            if k.lower() == 'appkey':
                secret = self._get_app_secret(params_dict[k])
        point_keys = []
        for key in params_dict.keys():
            if key.lower() in self.params_in_signature:
                point_keys.append(key)
        sorted_point_keys = sorted(point_keys)
        s = secret + '&'
        for i in sorted_point_keys:
            s += "%s=%s" % (i, params_dict[i])
            s += '&'
        s += secret
        signature = hashlib.sha1(s.encode('utf-8')).hexdigest().upper().strip()
        return signature
